﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using AndersLiu.Bambook.Snb;
using AndersLiu.MicrosoftOffice.PowerPoint;
using System.Diagnostics.CodeAnalysis;

namespace AndersLiu.Bambook.Ppt2Snb
{
    public partial class MainWindow : Form
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void MainWindow_Load(object sender, EventArgs e)
        {
            this.Text = Program.Title;
#if DEBUG
            AddFiles(Directory.GetFiles(@"D:\AndersLiu\Desktop\ppts"));
            txtOutputDir.Text = @"D:\Temp\bb\ppt2bb";
#endif
            prgProgress.Visible = false;
            lblProgress.Visible = false;
        }

        private void btnAddInputFile_Click(object sender, EventArgs e)
        {
            if (dlgOpenPpt.ShowDialog() != DialogResult.OK)
                return;

            AddFiles(dlgOpenPpt.FileNames);
        }

        private void btnRemoveInputFile_Click(object sender, EventArgs e)
        {
            if (lstInputFiles.SelectedItems.Count <= 0)
                return;

            foreach (ListViewItem item in lstInputFiles.SelectedItems)
                lstInputFiles.Items.Remove(item);
        }

        private void btnBrowseOutputDir_Click(object sender, EventArgs e)
        {
            if (dlgOpenOutputDir.ShowDialog() != DialogResult.OK)
                return;

            txtOutputDir.Text = dlgOpenOutputDir.SelectedPath;
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            if (!backgroundWorker.IsBusy)
            {
                if (lstInputFiles.Items.Count <= 0)
                {
                    ShowError("未添加任何文件。");
                    return;
                }

                var outDir = txtOutputDir.Text.Trim();
                if (string.IsNullOrEmpty(outDir))
                {
                    ShowError("未提供输出目录。");
                    return;
                }

                if (!Directory.Exists(outDir))
                {
                    var r = MessageBox.Show(
                        string.Format("{0} 目录不存在，是否创建？", outDir), "提示",
                        MessageBoxButtons.YesNo, MessageBoxIcon.Question);

                    if (r == DialogResult.Yes)
                        Directory.CreateDirectory(outDir);
                    else
                        return;
                }

                btnStart.Enabled = false;

                var inputFiles = new string[lstInputFiles.Items.Count];
                var p = 0;
                foreach (ListViewItem item in lstInputFiles.Items)
                    inputFiles[p++] = ((FileInfo)item.Tag).FullName;

                var args = new BackgroundWorkerArgs
                {
                    InputFiles = inputFiles,
                    OutputDir = outDir,
                    OverrideExists = chkOverride.Checked,
                };

                backgroundWorker.RunWorkerAsync(args);

                this.AcceptButton = null;
                this.CancelButton = btnStart;
                btnStart.Text = "取消(&C)";
                btnStart.Image = Properties.Resources.Stop;
                btnStart.Enabled = true;

                prgProgress.Value = 0;
                prgProgress.Visible = true;
                lblProgress.Text = "";
                lblProgress.Visible = true;
                txtProgress.Clear();
            }
            else  // backgroundWorker.IsBusy
            {
                var r = MessageBox.Show(
                    "是否要停止转换？",
                    "提示",
                    MessageBoxButtons.YesNo,
                    MessageBoxIcon.Question,
                    MessageBoxDefaultButton.Button2);

                if (r == DialogResult.Yes)
                {
                    btnStart.Enabled = false;
                    backgroundWorker.CancelAsync();
                }
            }
        }

        private void lstInputFiles_SelectedIndexChanged(object sender, EventArgs e)
        {
            btnRemoveInputFile.Enabled = lstInputFiles.SelectedItems.Count > 0;
        }

        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            var args = (BackgroundWorkerArgs)e.Argument;
            var result = new BackgroundWorkerResults();
            result.Total = args.InputFiles.Length;

            var perFileProgress = 1 / (double)result.Total;
            var fileProgress = 0.0;
            var progress = 0.0;

            for (var iFile = 0; iFile < args.InputFiles.Length; iFile++)
            {
                var file = args.InputFiles[iFile];
                fileProgress = iFile * perFileProgress;
                progress = fileProgress;

                if (backgroundWorker.CancellationPending)
                {
                    //e.Cancel = true;
                    e.Result = result;
                    return;
                }

                backgroundWorker.ReportProgress((int)(progress * 100),
                    new BackgroundWorkerProgress
                    {
                        Type = BackgroundWorkerProgressType.Log,
                        Data = file,
                    });

                backgroundWorker.ReportProgress((int)(progress * 100),
                    new BackgroundWorkerProgress
                    {
                        Type = BackgroundWorkerProgressType.Progress_PPT,
                        Data = null,
                    });

                try
                {
                    var fi = new FileInfo(file);
                    var outFile = Path.Combine(args.OutputDir, fi.Name + ".snb");

                    if (!args.OverrideExists && File.Exists(outFile))
                        throw new InvalidOperationException("文件已存在。");

                    var ppt = new PPTFile(file);
                    var snb = new SnbFile();
                    snb.Name = fi.Name;
                    snb.Author = "PPT2SNB";
                    snb.Generator = "PPT2SNB";

                    var ch = new SnbChapter(fi.Name);
                    var nPPT = 0;
                    foreach (var s in ppt.EnumSimpleSlides(700, 540))
                    {
                        progress = fileProgress + perFileProgress * 0.5 * nPPT / ppt.SlidesCount;
                        backgroundWorker.ReportProgress((int)(progress * 100),
                            new BackgroundWorkerProgress
                            {
                                Type = BackgroundWorkerProgressType.Progress_PPT_Slides,
                                Data = new BackgroundWorkerProgress_NumberVsTotal
                                {
                                    Number = nPPT + 1,
                                    Total = ppt.SlidesCount,
                                },
                            });

                        if (backgroundWorker.CancellationPending)
                        {
                            //e.Cancel = true;
                            e.Result = result;
                            return;
                        }

                        ch.Paragraphs.Add(new SnbImageParagraph(s.Picture));
                        if (!string.IsNullOrEmpty(s.Notes))
                            ch.Paragraphs.Add(new SnbTextParagraph(s.Notes));
                        nPPT++;
                    }
                    snb.Chapters.Add(ch);

                    if (backgroundWorker.CancellationPending)
                        break;

                    SnbProgressChangedCallback cb = (p, nc, ni, ti) =>
                    {
                        switch (p)
                        {
                            case SnbProgress.Initializing:
                                backgroundWorker.ReportProgress((int)(progress * 100),
                                    new BackgroundWorkerProgress
                                    {
                                        Type = BackgroundWorkerProgressType.Progress_SNB,
                                        Data = SnbProgress.Initializing,
                                    });
                                break;

                            case SnbProgress.SavingBookSnbf:
                                backgroundWorker.ReportProgress((int)(progress * 100),
                                    new BackgroundWorkerProgress
                                    {
                                        Type = BackgroundWorkerProgressType.Progress_SNB,
                                        Data = SnbProgress.SavingBookSnbf,
                                    });
                                break;

                            case SnbProgress.SavingTocSnbf:
                                backgroundWorker.ReportProgress((int)(progress * 100),
                                    new BackgroundWorkerProgress
                                    {
                                        Type = BackgroundWorkerProgressType.Progress_SNB,
                                        Data = SnbProgress.SavingTocSnbf,
                                    });
                                break;

                            case SnbProgress.Packing:
                                backgroundWorker.ReportProgress((int)(progress * 100),
                                    new BackgroundWorkerProgress
                                    {
                                        Type = BackgroundWorkerProgressType.Progress_SNB,
                                        Data = SnbProgress.Packing,
                                    });
                                break;

                            case SnbProgress.Verifying:
                                backgroundWorker.ReportProgress((int)(progress * 100),
                                    new BackgroundWorkerProgress
                                    {
                                        Type = BackgroundWorkerProgressType.Progress_SNB,
                                        Data = SnbProgress.Verifying,
                                    });
                                break;

                            case SnbProgress.SavingChapters:
                                backgroundWorker.ReportProgress((int)(progress * 100),
                                    new BackgroundWorkerProgress
                                    {
                                        Type = BackgroundWorkerProgressType.Progress_SNB,
                                        Data = SnbProgress.SavingChapters,
                                    });
                                break;

                            case SnbProgress.SavingImages:
                                progress = fileProgress + perFileProgress * 0.5
                                    + perFileProgress * 0.5 * ni / ti;
                                backgroundWorker.ReportProgress((int)(progress * 100),
                                    new BackgroundWorkerProgress
                                    {
                                        Type = BackgroundWorkerProgressType.Progress_SNB_IMG,
                                        Data = new BackgroundWorkerProgress_NumberVsTotal
                                        {
                                            Number = ni,
                                            Total = ti,
                                        },
                                    });
                                break;

                            default:
                                break;
                        }

                        if (backgroundWorker.CancellationPending)
                        {
                            //e.Cancel = true;
                            e.Result = result;
                            throw new CancelSaveException();
                        }
                    };

                    try
                    {
                        snb.Save(outFile, cb);
                    }
                    catch (CancelSaveException)
                    {
                        // Saving cancelled.
                        return;
                    }

                    result.Success++;
                }
                catch (Exception ex)
                {
                    result.Errors++;
#if DEBUG
                    var msg = ex.ToString();
#else
                    var msg = ex.Message;
#endif
                    backgroundWorker.ReportProgress((int)(progress * 100),
                        new BackgroundWorkerProgress
                        {
                            Type = BackgroundWorkerProgressType.Error,
                            Data = msg,
                        });
                }
            }

            e.Result = result;
        }

        private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            prgProgress.Value = e.ProgressPercentage > 100 ? 100 : e.ProgressPercentage;
            var p = (BackgroundWorkerProgress)e.UserState;
            switch (p.Type)
            {
                case BackgroundWorkerProgressType.Log:
                    ProgressAppendColoredLine(Color.Black, "正在处理：" + p.Data);
                    break;

                case BackgroundWorkerProgressType.Error:
                    ProgressAppendColoredLine(Color.Red, "错误：" + p.Data);
                    break;

                case BackgroundWorkerProgressType.Progress_PPT:
                    lblProgress.Text = "正在准备PPT文件...";
                    break;

                case BackgroundWorkerProgressType.Progress_PPT_Slides:
                    var nt = (BackgroundWorkerProgress_NumberVsTotal)p.Data;
                    lblProgress.Text = string.Format("正在处理PPT文件... (第{0}页/共{1}页)",
                        nt.Number, nt.Total);
                    break;

                case BackgroundWorkerProgressType.Progress_SNB:
                    var sp = (SnbProgress)p.Data;
                    switch (sp)
                    {
                        case SnbProgress.Initializing:
                            lblProgress.Text = "正在初始化SNB文件...";
                            break;

                        case SnbProgress.SavingBookSnbf:
                            lblProgress.Text = "正在保存book.snbf文件...";
                            break;

                        case SnbProgress.SavingTocSnbf:
                            lblProgress.Text = "正在保存toc.snbf文件...";
                            break;

                        case SnbProgress.SavingChapters:
                            lblProgress.Text = "正在保存章节...";
                            break;

                        case SnbProgress.Packing:
                            lblProgress.Text = "正在打包SNB文件...";
                            break;

                        case SnbProgress.Verifying:
                            lblProgress.Text = "正在验证SNB文件...";
                            break;

                        default:
                            lblProgress.Text = "";
                            break;
                    }
                    break;

                case BackgroundWorkerProgressType.Progress_SNB_IMG:
                    var imnt = (BackgroundWorkerProgress_NumberVsTotal)p.Data;
                    lblProgress.Text = string.Format("正在保存图片... ({0}/{1})",
                        imnt.Number, imnt.Total);
                    break;

                default:
                    break;
            }
        }

        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.AcceptButton = btnStart;
            this.CancelButton = null;
            btnStart.Text = "开始(&S)";
            btnStart.Image = Properties.Resources.Run;
            btnStart.Enabled = true;

            prgProgress.Visible = false;
            lblProgress.Visible = false;

            if (e.Error != null)
            {
#if DEBUG
                var msg = e.Error.ToString();
#else
                var msg = e.Error.Message;
#endif
                ProgressAppendColoredLine(Color.Red, "转换过程中发生未知错误：");
                ProgressAppendColoredLine(Color.Red, msg);
            }

            var result = (BackgroundWorkerResults)e.Result;
            var nSuccess = result.Success;
            var nErrors = result.Errors;
            var nSkipped = result.Total - result.Success - result.Errors;
            if (nSkipped > 0)
                ProgressAppendColoredLine(Color.DarkOrange, "操作已取消。");

            ProgressAppendColoredLine(Color.Black, "--------");
            if (nSuccess > 0)
                ProgressAppendColoredLine(Color.Green,
                    string.Format("{0} 成功。", result.Success));
            if (nErrors > 0)
                ProgressAppendColoredLine(Color.Red,
                    string.Format("{0} 错误。", nErrors));
            if (nSkipped > 0)
                ProgressAppendColoredLine(Color.DarkOrange,
                    string.Format("{0} 未处理。", nSkipped));
        }

        private void ProgressAppendColoredLine(Color color, string line)
        {
            var start = txtProgress.SelectionStart;
            var len = txtProgress.SelectionLength;

            txtProgress.Select(txtProgress.TextLength, 0);
            txtProgress.SelectionColor = color;
            txtProgress.AppendText(line);
            txtProgress.AppendText(Environment.NewLine);
            txtProgress.ScrollToCaret();

            txtProgress.SelectionStart = start;
            txtProgress.SelectionLength = len;
        }

        private void AddFiles(string[] files)
        {
            foreach (var path in files)
            {
                var f = new FileInfo(path);
                var item = new ListViewItem(f.Name);
                item.SubItems.Add(f.DirectoryName);
                item.Tag = f;
                lstInputFiles.Items.Add(item);
            }

            lstInputFiles.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
        }

        private void ShowException(Exception ex)
        {
#if DEBUG
            ShowError(ex.ToString());
#else
            ShowError(ex.Message);
#endif
        }

        private void ShowError(string msg)
        {
            MessageBox.Show(msg, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        private void ShowInfo(string msg)
        {
            MessageBox.Show(msg, "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        class BackgroundWorkerArgs
        {
            public string[] InputFiles;
            public string OutputDir;
            public bool OverrideExists;
        }

        class BackgroundWorkerResults
        {
            public int Success = 0;
            public int Errors = 0;
            public int Total = 0;
        }

        enum BackgroundWorkerProgressType
        {
            Progress_PPT,
            Progress_PPT_Slides,
            Progress_SNB,
            Progress_SNB_IMG,
            Log,
            Error,
        }

        class BackgroundWorkerProgress
        {
            public BackgroundWorkerProgressType Type;
            public object Data;
        }

        class BackgroundWorkerProgress_NumberVsTotal
        {
            public int Number;
            public int Total;
        }

        [SuppressMessage("Microsoft.Usage", "CA2237")]
        class CancelSaveException : Exception
        {
        }
    }
}
